package com.atguigu.gmall.seckill.service.impl;
import java.util.Date;
import java.math.BigDecimal;

import com.atguigu.gmall.common.feign.order.OrderFeignClient;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.enums.OrderStatus;
import com.atguigu.gmall.enums.ProcessStatus;
import com.atguigu.gmall.order.entity.OrderDetail;
import com.atguigu.gmall.rabbit.constant.RabbitConstant;
import com.atguigu.gmall.seckill.dto.OrderDetailListDto;
import com.atguigu.gmall.seckill.dto.SeckillOrderSubmitDto;
import com.google.common.collect.Lists;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.GmallConstant;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.feign.util.AuthUserInfoUtils;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.order.entity.OrderInfo;
import com.atguigu.gmall.seckill.entity.SeckillQueueMsg;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.gmall.seckill.entity.SeckillGoods;
import com.atguigu.gmall.seckill.service.SeckillGoodsService;
import com.atguigu.gmall.seckill.mapper.SeckillGoodsMapper;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
* @author xueyoupeng
* @description 针对表【seckill_goods】的数据库操作Service实现
* @createDate 2023-08-02 19:13:07
*/
@Service
public class SeckillGoodsServiceImpl extends ServiceImpl<SeckillGoodsMapper, SeckillGoods> implements SeckillGoodsService{

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;

    @Autowired
    private RedisTemplate<String , String> redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private OrderFeignClient orderFeignClient;

    private ConcurrentHashMap<Long , SeckillGoods> seckillGoodsConcurrentHashMap = new ConcurrentHashMap<>();

    @Override
    public List<SeckillGoods> findAllSeckillGoods() {
        //在此时间以内的都是满足需求的
        String dateStr = DateUtil.format(new Date(), "yyyy-MM-dd");
        return seckillGoodsMapper.findAllSeckillGoods(dateStr);

    }

    @Override
    public void saveSseckillGoodsToRedis(List<SeckillGoods> seckillGoodsList) {
        //构建大key
        String redisKey = buildRedisKey();
        seckillGoodsList.stream().forEach(seckillGoods -> {
            redisTemplate.opsForHash().put(redisKey , String.valueOf(seckillGoods.getSkuId()) , JSON.toJSONString(seckillGoods));

            //库存数据预热
            redisTemplate.opsForValue().set(GmallConstant.REDIS_SECKILL_STOCK_COUNT + seckillGoods.getSkuId() , String.valueOf(seckillGoods.getStockCount() * 3) , 2 , TimeUnit.DAYS);
        });
        redisTemplate.expire(redisKey , Duration.ofDays(2));
    }

    @Override
    public void saveSseckillGoodsToLocalCache(List<SeckillGoods> seckillGoodsList) {
        seckillGoodsConcurrentHashMap.clear();
        seckillGoodsList.stream().forEach(seckillGoods -> {
            seckillGoodsConcurrentHashMap.put(seckillGoods.getSkuId() , seckillGoods);
        });
    }

    @Override
    public List<SeckillGoods> findAll() {

        // 查询本地缓存
        Collection<SeckillGoods> seckillGoods = seckillGoodsConcurrentHashMap.values();
        if (seckillGoods.size() > 0){
            List<SeckillGoods> seckillGoodsList = seckillGoods.stream().collect(Collectors.toList());
            return seckillGoodsList;
        }

        //本地缓存中不存在数据，查询分布式缓存
        String redisKey = buildRedisKey();
        List<SeckillGoods> goodsList = redisTemplate.opsForHash().values(redisKey).stream().map(obj -> {
            String json = obj.toString();
            SeckillGoods goods = JSON.parseObject(json, SeckillGoods.class);
            return goods;
        }).collect(Collectors.toList());

        //把查询到的数据存储到本地缓存中
        goodsList.stream().forEach(goods -> {
            seckillGoodsConcurrentHashMap.put(goods.getSkuId() , goods);
        });

        return goodsList;
    }

    @Override
    public SeckillGoods findBySkuId(Long skuId) {

        // 从本地缓存中查询数据
        SeckillGoods seckillGoods = seckillGoodsConcurrentHashMap.get(skuId);
        if (seckillGoods != null){
            return seckillGoods;
        }

        // 从分布式缓存中查询数据
        String redisKey = buildRedisKey();
        Object obj = redisTemplate.opsForHash().get(redisKey, String.valueOf(skuId));
        if (obj != null){
            String json = obj.toString();
            SeckillGoods goods = JSON.parseObject(json, SeckillGoods.class);
            //查询出来先把他再放到本地缓存中
            seckillGoodsConcurrentHashMap.put(skuId , goods);
            return goods;
        }
        //都没有的话返回空
        return null;
    }

    //获取秒杀码
    @Override
    public String getSeckillSkuIdStr(Long skuId) {

        // 校验
        SeckillGoods seckillGoods = findBySkuId(skuId);
        long startTime = seckillGoods.getStartTime().getTime();
        //获取当前系统时间的毫秒数
        long currentTime = System.currentTimeMillis();
        if (currentTime < startTime){
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START) ;
        }

        long endTime = seckillGoods.getEndTime().getTime();
        if (currentTime > endTime){
            throw new GmallException(ResultCodeEnum.SECKILL_END) ;
        }
        //校验剩余库存数量
        Integer stockCount = seckillGoods.getStockCount();
        if (stockCount <= 0){
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH) ;
        }


        // 生成秒杀码  MD5(当天日期字符串-当前登录用户id-秒杀商品skuId)
        String seckillCode =  genSeckillCode(skuId) ;

        // 需要将秒杀码存储到Redis中
        //先得到key
        String seckillCodeRedisKey = GmallConstant.REDIS_SECKILL_CODE_PRE + seckillCode;
        if (!redisTemplate.hasKey(seckillCodeRedisKey)){
            redisTemplate.opsForValue().set(seckillCodeRedisKey , "1" , 1 , TimeUnit.DAYS);
        }

        return seckillCode;
    }

    @Override
    public void queue(Long skuId, String skuIdStr) {

        //* 校验传递过来的秒杀码是否是当前登录用户这一天的秒杀码
        String seckillSkuIdStr = getSeckillSkuIdStr(skuId);
        if (!seckillSkuIdStr.equals(skuIdStr)){
            throw new GmallException(ResultCodeEnum.SECKILL_ILLEGAL) ;
        }

        //* 校验是否是通过正常流程生成了秒杀码，正常流程在Redis中肯定会存在该秒杀码
        //Redis中存的秒杀码的大key是前缀GmallConstant.REDIS_SECKILL_CODE_PRE + 秒杀码
        String redisKey = GmallConstant.REDIS_SECKILL_CODE_PRE + skuIdStr;
        if (!redisTemplate.hasKey(redisKey)){
            throw new GmallException(ResultCodeEnum.SECKILL_ILLEGAL) ;
        }

        //* 校验秒杀活动是否开始
        //先拿到该商品
        SeckillGoods seckillGoods = findBySkuId(skuId);
        if (seckillGoods == null){
            throw new GmallException(ResultCodeEnum.SECKILL_ILLEGAL);
        }
        //拿到当前时间
        long currentTime = System.currentTimeMillis();
        //开始时间
        long startTime = seckillGoods.getStartTime().getTime();
        if (startTime > currentTime){
            throw new GmallException(ResultCodeEnum.SECKILL_NO_START) ;
        }
        //结束时间
        long endTime = seckillGoods.getEndTime().getTime();
        if (endTime < currentTime){
            throw new GmallException(ResultCodeEnum.SECKILL_END) ;
        }

        //* 校验商品是否存在库存
        Integer stockCount = seckillGoods.getStockCount();
        if (stockCount <= 0){
            throw new GmallException(ResultCodeEnum.SECKILL_FINISH) ;
        }

        // 对秒杀码的值进行+1
        //根据大key自增1
        Long increment = redisTemplate.opsForValue().increment(GmallConstant.REDIS_SECKILL_CODE_PRE + skuIdStr);
        if (increment <= 2){

            //进行库存的预扣减：本地缓存 Redis
            seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);

            //Redis缓存扣减
            Long decrement = redisTemplate.opsForValue().decrement(GmallConstant.REDIS_SECKILL_STOCK_COUNT + skuId);

            if (decrement >= 0){

                //发送抢购消息
                SeckillQueueMsg seckillQueueMsg = new SeckillQueueMsg();
                String userId = AuthUserInfoUtils.getAuthUserInfo().getUserId();
                seckillQueueMsg.setSeckillCode(skuIdStr);
                seckillQueueMsg.setUserId(Long.parseLong(userId));
                seckillQueueMsg.setSkuId(skuId);

                // 发送消息
                rabbitTemplate.convertAndSend(RabbitConstant.SECKILL_ORDER_EXCHANGE , RabbitConstant.SECKILL_ORDER_ROUTING_KEY ,JSON.toJSONString(seckillQueueMsg));

            }else {
                throw new GmallException(ResultCodeEnum.SECKILL_FINISH) ;
            }
        }
    }

    @Override
    public void deduceStock(SeckillQueueMsg seckillQueueMsg) {

        //对库存数进行 -1 操作
        Long skuId = seckillQueueMsg.getSkuId();
        LambdaQueryWrapper<SeckillGoods> seckillGoodsLambdaQueryWrapper = new LambdaQueryWrapper<>();
        seckillGoodsLambdaQueryWrapper.eq(SeckillGoods::getSkuId , skuId);
        SeckillGoods seckillGoods = this.getOne(seckillGoodsLambdaQueryWrapper);
        seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);
        this.updateById(seckillGoods);
    }

    @Override
    public void saveOrderInfoToRedis(SeckillQueueMsg seckillQueueMsg) {

        OrderInfo orderInfo = new OrderInfo();

        // 获取秒杀商品数据
        SeckillGoods seckillGoods = findBySkuId(seckillQueueMsg.getSkuId());
        orderInfo.setTotalAmount(seckillGoods.getCostPrice());
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        orderInfo.setUserId(seckillQueueMsg.getUserId());
        orderInfo.setPaymentWay("ONLINE");
        String replaceAll = UUID.randomUUID().toString().replaceAll("-", "");
        orderInfo.setOutTradeNo(replaceAll);
        orderInfo.setTradeBody(seckillGoods.getSkuName());
        orderInfo.setCreateTime(new Date());
        Long expireTime = System.currentTimeMillis() + 1000 * 60 * 30;
        orderInfo.setExpireTime(new Date(expireTime));
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        orderInfo.setImgUrl(seckillGoods.getSkuDefaultImg());
        orderInfo.setOperateTime(new Date());
        orderInfo.setActivityReduceAmount(seckillGoods.getPrice().subtract(seckillGoods.getCostPrice()));
        orderInfo.setOriginalTotalAmount(seckillGoods.getPrice());

        // 订单明细
        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setUserId(seckillQueueMsg.getUserId());
        orderDetail.setSkuId(seckillQueueMsg.getSkuId());
        orderDetail.setSkuName(seckillGoods.getSkuName());
        orderDetail.setImgUrl(seckillGoods.getSkuDefaultImg());
        orderDetail.setOrderPrice(seckillGoods.getCostPrice().intValue());
        orderDetail.setSkuNum("1");
        orderDetail.setCreateTime(new Date());
        orderDetail.setSplitTotalAmount(seckillGoods.getCostPrice());
        orderDetail.setSplitActivityAmount(orderInfo.getActivityReduceAmount());

        orderInfo.setOrderDetailList(Arrays.asList(orderDetail));

        //把订单数据存储到Redis中
        String redisOrderKey = GmallConstant.REDIS_SECKILL_ORDER + seckillQueueMsg.getSeckillCode();
        redisTemplate.opsForValue().set(redisOrderKey , JSON.toJSONString(orderInfo) , 2 , TimeUnit.DAYS);
    }

    @Override
    public void redisStockCountDeduce(SeckillQueueMsg seckillQueueMsg) {
        String redisKey = buildRedisKey();
        String seckillGoodsJson = redisTemplate.opsForHash().get(redisKey, String.valueOf(seckillQueueMsg.getSkuId())).toString();
        SeckillGoods seckillGoods = JSON.parseObject(seckillGoodsJson, SeckillGoods.class);
        seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);
        redisTemplate.opsForHash().put(redisKey , String.valueOf(seckillQueueMsg.getSkuId()) , JSON.toJSONString(seckillGoods));
    }

    @Override
    public ResultCodeEnum checkOrder(Long skuId) {

        //redis的数据key
        String seckillCode = genSeckillCode(skuId);
        String redisOrderKey = GmallConstant.REDIS_SECKILL_ORDER + seckillCode;
        String orderJSON = redisTemplate.opsForValue().get(redisOrderKey);
        if (StringUtils.isEmpty(orderJSON)){
            return ResultCodeEnum.SECKILL_RUN ;
        }else {
            if (GmallConstant.REDIS_NULL_VALUE.equalsIgnoreCase(orderJSON)){
                return ResultCodeEnum.SECKILL_FINISH ;
            }else {
                OrderInfo orderInfo = JSON.parseObject(orderJSON, OrderInfo.class);
                if (orderInfo.getId() == null){
                    return ResultCodeEnum.SECKILL_SUCCESS ;
                }else {
                    return ResultCodeEnum.SECKILL_ORDER_SUCCESS ;
                }
            }
        }
    }

    @Override
    public OrderInfo findOrderInfoBySkuId(Long skuId) {
        //从Redis中查询
        //先拿到秒杀码
        String seckillCode = genSeckillCode(skuId);
        String redisKey = GmallConstant.REDIS_SECKILL_ORDER + seckillCode;
        String orderInfoStr = redisTemplate.opsForValue().get(redisKey);
        OrderInfo orderInfo = JSON.parseObject(orderInfoStr, OrderInfo.class);
        return orderInfo;
    }

    @Override
    public String submitOrder(SeckillOrderSubmitDto seckillOrderSubmitDto) {

        // 1、从Redis中查询出订单数据
        //要得到RedisKey 就要有skuId
        OrderDetailListDto orderDetailListDto = seckillOrderSubmitDto.getOrderDetailList().get(0);
        Long skuId = orderDetailListDto.getSkuId();
        String seckillCode = genSeckillCode(skuId);
        String orderInfoJSON = redisTemplate.opsForValue().get(GmallConstant.REDIS_SECKILL_ORDER + seckillCode);
        OrderInfo orderInfo = JSON.parseObject(orderInfoJSON, OrderInfo.class);

        // 2、补全订单数据
        orderInfo.setConsignee(seckillOrderSubmitDto.getConsignee());
        orderInfo.setConsigneeTel(seckillOrderSubmitDto.getConsigneeTel());
        orderInfo.setDeliveryAddress(seckillOrderSubmitDto.getDeliveryAddress());
        orderInfo.setOrderComment(seckillOrderSubmitDto.getOrderComment());


        // 3、远程调用service-order微服务的接口，把秒杀订单数据保存到订单数据库
        Result<String> stringResult = orderFeignClient.saveOrderInfo(orderInfo);
        String orderId = stringResult.getData();

        // 4、把订单数据保存到Redis中
        orderInfo.setId(Long.parseLong(orderId));
        //设置订单id
        orderInfo.getOrderDetailList().get(0).setOrderId(Long.parseLong(orderId));
        redisTemplate.opsForValue().set(GmallConstant.REDIS_SECKILL_ORDER + seckillCode ,JSON.toJSONString(orderInfo) , 2 , TimeUnit.DAYS);

        // 5、返回订单的id
        return orderId;
    }

    private String genSeckillCode(Long skuId) {
        String dateStr = DateUtil.format(new Date(), "yyyy-MM-dd");
        //拿到userId
        String userId = AuthUserInfoUtils.getAuthUserInfo().getUserId();
        //拼接成字符串
        String seckillCodeStr = dateStr + "-" + userId + "-" + skuId;
        //用md5加密一下
        String seckillCode = DigestUtils.md5DigestAsHex(seckillCodeStr.getBytes());
        return seckillCode;
    }

    private String buildRedisKey() {
        //格式：前缀+时间
        String dateStr = DateUtil.format(new Date(), "yyyy-MM-dd");
        String seckillGoodsRedisKey = GmallConstant.REDIS_ORDER_SECKILL + dateStr;
        return seckillGoodsRedisKey;
    }
}




